from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import sessionmaker, Session, relationship, declarative_base
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

# 数据库配置
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# 关联表
# 用户角色关联表
user_role = Table(
    "user_role",
    Base.metadata,
    Column("user_id", Integer, ForeignKey("users.id")),
    Column("role_id", Integer, ForeignKey("roles.id"))
)

# 角色权限关联表
role_permission = Table(
    "role_permission",
    Base.metadata,
    Column("role_id", Integer, ForeignKey("roles.id")),
    Column("permission_id", Integer, ForeignKey("permissions.id"))
)


# 数据库模型
class User(Base):
    """
    用户信息
    """
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, index=True)
    hashed_password = Column(String(128))
    roles = relationship("Role", secondary=user_role, back_populates="users")


class Role(Base):
    """
    角色信息
    """
    __tablename__ = "roles"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), unique=True)
    description = Column(String(255))
    users = relationship("User", secondary=user_role, back_populates="roles")
    permissions = relationship("Permission", secondary=role_permission, back_populates="roles")


class Permission(Base):
    """
    权限信息
    """
    __tablename__ = "permissions"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), unique=True)  # 格式: 资源:操作 如 article:delete
    description = Column(String(255))
    roles = relationship("Role", secondary=role_permission, back_populates="permissions")


# 创建数据库表
Base.metadata.create_all(bind=engine)


# Pydantic模型
class UserCreate(BaseModel):
    username: str
    password: str


class UserOut(BaseModel):
    id: int
    username: str
    roles: List[str] = []

    class Config:
        orm_mode = True


class RoleCreate(BaseModel):
    name: str
    description: Optional[str] = None


class PermissionCreate(BaseModel):
    name: str
    description: Optional[str] = None


class Token(BaseModel):
    access_token: str
    token_type: str


class UserInfo(BaseModel):
    username: str
    roles: List[str]
    permissions: List[str]


# 安全配置
# 生产环境应使用安全随机字符串
SECRET_KEY = "FBuLrbuoyALDFW5COKmxXSKB29aQ7rgu0BLydaQcLqcPT6MEwj0bFaeXVxfMGa9c"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# 工具函数
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


# 数据库依赖
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    """
    获取当前用户
    :param token:
    :param db:
    :return:
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    return user


def require_permissions(required_perms: List[str]):
    """
    权限装饰器实现
    :param required_perms:
    :return:
    """

    async def permission_checker(
            current_user: User = Depends(get_current_user),
            db: Session = Depends(get_db)
    ):
        user_perms = set()
        for role in current_user.roles:
            for perm in role.permissions:
                user_perms.add(perm.name)
        if not user_perms.issuperset(required_perms):
            raise HTTPException(status_code=403, detail="权限不足")
        return current_user

    return permission_checker


# FastAPI应用
app = FastAPI()


@app.on_event("startup")
def startup_event():
    """
    初始化
    :return:
    """
    db = SessionLocal()
    try:
        # 初始化管理员账号
        admin_user = db.query(User).filter(User.username == "admin").first()
        if not admin_user:
            hashed_password = get_password_hash("admin123")  # 默认管理员密码
            admin_user = User(username="admin", hashed_password=hashed_password)
            db.add(admin_user)
            db.commit()
            db.refresh(admin_user)
            print("管理员账号已创建: admin/admin123")

        # 初始化普通用户账号
        regular_user = db.query(User).filter(User.username == "user").first()
        if not regular_user:
            hashed_password = get_password_hash("user123")  # 默认用户密码
            regular_user = User(username="user", hashed_password=hashed_password)
            db.add(regular_user)
            db.commit()
            db.refresh(regular_user)
            print("普通用户账号已创建: user/user123")

        # 初始化管理员角色和权限
        init_roles_and_permissions(db)
    finally:
        db.close()


def init_roles_and_permissions(db: Session):
    """
    初始化角色和权限
    :param db:
    :return:
    """
    # 创建管理员角色
    admin_role = db.query(Role).filter(Role.name == "admin").first()
    if not admin_role:
        admin_role = Role(name="admin", description="系统管理员")
        db.add(admin_role)
        db.commit()

    # 创建普通用户角色
    user_role = db.query(Role).filter(Role.name == "user").first()
    if not user_role:
        user_role = Role(name="user", description="普通用户")
        db.add(user_role)
        db.commit()

    # 创建权限
    permissions = [
        ("admin:dashboard", "访问管理员面板"),
        ("article:create", "创建文章"),
        ("article:delete", "删除文章"),
        ("article:view", "查看文章")
    ]

    for perm_name, description in permissions:
        perm = db.query(Permission).filter(Permission.name == perm_name).first()
        if not perm:
            perm = Permission(name=perm_name, description=description)
            db.add(perm)

    db.commit()

    # 为管理员角色分配所有权限
    admin_perms = db.query(Permission).all()
    for perm in admin_perms:
        if perm not in admin_role.permissions:
            admin_role.permissions.append(perm)

    # 为普通用户角色分配基本权限
    basic_perms = ["article:view", "article:create"]
    for perm_name in basic_perms:
        perm = db.query(Permission).filter(Permission.name == perm_name).first()
        if perm and perm not in user_role.permissions:
            user_role.permissions.append(perm)

    db.commit()

    # 将角色分配给用户
    admin_user = db.query(User).filter(User.username == "admin").first()
    if admin_user and admin_role not in admin_user.roles:
        admin_user.roles.append(admin_role)

    regular_user = db.query(User).filter(User.username == "user").first()
    if regular_user and user_role not in regular_user.roles:
        regular_user.roles.append(user_role)

    db.commit()


@app.post("/register", response_model=UserOut)
def register_user(user: UserCreate, db: Session = Depends(get_db)):
    """
    用户注册
    :param user:
    :param db:
    :return:
    """
    db_user = db.query(User).filter(User.username == user.username).first()
    if db_user:
        raise HTTPException(status_code=400, detail="用户名已存在")

    hashed_password = get_password_hash(user.password)
    new_user = User(username=user.username, hashed_password=hashed_password)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user


@app.post("/token", response_model=Token)
def login_for_access_token(
        form_data: OAuth2PasswordRequestForm = Depends(),
        db: Session = Depends(get_db)
):
    """
    登录获取Token
    :param form_data:
    :param db:
    :return:
    """
    user = db.query(User).filter(User.username == form_data.username).first()
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )

    access_token = create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}


@app.post("/roles")
def create_role(role: RoleCreate, db: Session = Depends(get_db)):
    """
    创建角色
    :param role:
    :param db:
    :return:
    """
    db_role = db.query(Role).filter(Role.name == role.name).first()
    if db_role:
        raise HTTPException(status_code=400, detail="角色已存在")

    new_role = Role(name=role.name, description=role.description)
    db.add(new_role)
    db.commit()
    db.refresh(new_role)
    return {"message": "角色创建成功", "role": role.name}


@app.post("/permissions")
def create_permission(permission: PermissionCreate, db: Session = Depends(get_db)):
    """
    创建权限
    :param permission:
    :param db:
    :return:
    """
    db_perm = db.query(Permission).filter(Permission.name == permission.name).first()
    if db_perm:
        raise HTTPException(status_code=400, detail="权限已存在")

    new_perm = Permission(name=permission.name, description=permission.description)
    db.add(new_perm)
    db.commit()
    db.refresh(new_perm)
    return {"message": "权限创建成功", "permission": permission.name}


@app.post("/users/{user_id}/roles/{role_id}")
def assign_role_to_user(user_id: int, role_id: int, db: Session = Depends(get_db)):
    """
    给用户分配角色
    :param user_id:
    :param role_id:
    :param db:
    :return:
    """
    user = db.query(User).get(user_id)
    role = db.query(Role).get(role_id)

    if not user or not role:
        raise HTTPException(status_code=404, detail="用户或角色不存在")

    if role not in user.roles:
        user.roles.append(role)
        db.commit()

    return {"message": f"已为用户 {user.username} 分配角色 {role.name}"}


@app.post("/roles/{role_id}/permissions/{perm_id}")
def assign_permission_to_role(role_id: int, perm_id: int, db: Session = Depends(get_db)):
    """
    给角色分配权限
    :param role_id:
    :param perm_id:
    :param db:
    :return:
    """
    role = db.query(Role).get(role_id)
    permission = db.query(Permission).get(perm_id)

    if not role or not permission:
        raise HTTPException(status_code=404, detail="角色或权限不存在")

    if permission not in role.permissions:
        role.permissions.append(permission)
        db.commit()

    return {"message": f"已为角色 {role.name} 分配权限 {permission.name}"}


@app.get("/user/profile", response_model=UserOut)
def get_user_profile(current_user: User = Depends(get_current_user)):
    """
    受保护的API示例
    :param current_user:
    :return:
    """
    return current_user


@app.delete("/articles/{article_id}")
def delete_article(
        article_id: int,
        _=Depends(require_permissions(["article:delete"]))
):
    """
    删除文章
    :param article_id:
    :param _:
    :return:
    """
    # 实际项目中这里会有删除文章的业务逻辑
    return {"message": f"文章 {article_id} 已删除"}


@app.post("/articles")
def create_article(
        _=Depends(require_permissions(["article:create"]))
):
    """
    创建文章
    :param _:
    :return:
    """
    # 创建文章的业务逻辑
    return {"message": "文章创建成功"}


@app.get("/admin/dashboard")
def admin_dashboard(
        _=Depends(require_permissions(["admin:dashboard"]))
):
    """
    访问管理员面板
    :param _:
    :return:
    """
    return {"message": "欢迎访问管理员面板"}


@app.get("/users/me", response_model=UserInfo)
async def read_users_me(
        current_user: User = Depends(get_current_user),
        db: Session = Depends(get_db)
):
    """
    获取用户所有权限
    :param current_user:
    :param db:
    :return:
    """
    # 获取用户所有权限
    all_permissions = set()
    for role in current_user.roles:
        for perm in role.permissions:
            all_permissions.add(perm.name)

    return UserInfo(
        username=current_user.username,
        roles=[role.name for role in current_user.roles],
        permissions=list(all_permissions)
    )
